﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using Bio.Data.Interfaces;
using LabelRNA;

namespace Bio.Data.Providers.Structure.XMLStr
{
    /// <summary>
    /// This loads the xml-based structure format
    /// </summary>
    class XMLStrFile : BioFileDataLoader<IStructureModelBioEntity>
    {
        private SimpleRNASequence _sequence;
        private Dictionary<int, int> _basePairs;
        private List<IBasePairBioEntity> _basePairEntities;
        private RNASecondaryStructureNucleotideTextShape _globalstructureModelNucleotideTextRendering;
        private RNASecondaryStructureNucleotideCircleShape _globalstructureModelNucleotideCircleRendering;
        private RNASecondaryStructureBasePairConnector _globalstructureModelBasePairConnectorRendering;
        private NucleotideMetadata.RenderingMode? _globalstructureModelNucleotideRenderingMode = null;
        private BasePairMetadata.ConnectorRenderingMode? _globalstructureModelBasePairConnectorRenderingMode;
        private bool? _globalstructureModelBasePairConnectorIsVisible;

        protected override bool OnInitialize()
        {
            if (base.OnInitialize())
            {
                using (var filestream = new FileStream(InitializationData, FileMode.Open))
                {
                    XDocument strmdlxml = XDocument.Load(filestream);

                    //Basic validation.
                    if(strmdlxml == null ||
                       strmdlxml.Root==null ||
                       !strmdlxml.Root.Name.LocalName.Equals(RNAStructureModelXMLTags.StructureModelLabel))
                        return false;

                    //1. We first try to extract and load the sequence first.
                    var strmdlseqlength = strmdlxml.Root.Attribute(RNAStructureModelXMLTags.StructureModelSequenceLengthAttribute);

                    int sequenceLength = -1;
                    if(strmdlseqlength == null ||
                       strmdlseqlength.Value == null ||
                       !Int32.TryParse(strmdlseqlength.Value, out sequenceLength))
                        return false;

                    var strmdlseq = strmdlxml.Root.Element(RNAStructureModelXMLTags.StructureModelSequenceLabel);

                    if (!(strmdlseq != null &&
                          !strmdlseq.IsEmpty &&
                          strmdlseq.Value.Length == sequenceLength))
                        return false;

                    _sequence = new SimpleRNASequence();
                    foreach (var nt in strmdlseq.Value.Select(ntChar => ntChar))
                        _sequence.AddSymbol(nt);

                    //2. We try to extract out the pairs
                    _basePairs = new Dictionary<int, int>();
                    _basePairEntities = new List<IBasePairBioEntity>();
                    var strmdlpairs = strmdlxml.Root.Element(RNAStructureModelXMLTags.StructureModelBasePairsLabel);
                    if (strmdlpairs != null)
                    {
                        foreach (var strmdlpair in strmdlpairs.Descendants(RNAStructureModelXMLTags.StructureModelPairLabel))
                        {
                            var strmdlpairfpidx = strmdlpair.Element(RNAStructureModelXMLTags.StructureModelPairFivePrimeIndexLabel);
                            var strmdlpairtpidx = strmdlpair.Element(RNAStructureModelXMLTags.StructureModelPairThreePrimeIndexLabel);

                            int idx1 = -1;
                            int idx2 = -1;

                            if (strmdlpairfpidx != null &&
                                strmdlpairtpidx != null &&
                                !strmdlpairfpidx.IsEmpty &&
                                !strmdlpairtpidx.IsEmpty &&
                                Int32.TryParse(strmdlpairfpidx.Value, out idx1) &&
                                Int32.TryParse(strmdlpairtpidx.Value, out idx2) &&
                                (idx1 >= 0 && idx1 < sequenceLength) &&
                                (idx2 >= 0 && idx2 < sequenceLength))
                            {
                                if (idx1 == idx2) { continue; } //Error, a nucleotide can't be paired to itself, skip it
                                if (idx1 < idx2)
                                {
                                    //We have to translate from the 1s-based file format
                                    idx1 = idx1 - 1;
                                    idx2 = idx2 - 1;
                                    _basePairs.Add(idx1, idx2);
                                    RNASecondaryStructureBasePair bpObj = new RNASecondaryStructureBasePair(_sequence, idx1, idx2);
                                    BasePairMetadata bpMetadata = null; 
                                    
                                    var strmdlpairconnectorvisible = strmdlpair.Element(RNAStructureModelXMLTags.StructureModelPairConnectorVisibleLabel);
                                    bool bConnVisible = true;
                                    if (strmdlpairconnectorvisible != null &&
                                        !strmdlpairconnectorvisible.IsEmpty &&
                                        bool.TryParse(strmdlpairconnectorvisible.Value, out bConnVisible))
                                    {
                                        if (bpMetadata == null)
                                            bpMetadata = new BasePairMetadata();

                                        bpMetadata.ConnectorVisible = bConnVisible;
                                    }

                                    var strmdlpairconnectorrenderingstyle = strmdlpair.Element(RNAStructureModelXMLTags.StructureModelPairConnectorRenderingStyleLabel);
                                    BasePairMetadata.ConnectorRenderingMode connRenderingStyle;
                                    if(strmdlpairconnectorrenderingstyle != null &&
                                        !strmdlpairconnectorrenderingstyle.IsEmpty &&
                                        Enum.TryParse<BasePairMetadata.ConnectorRenderingMode>(strmdlpairconnectorrenderingstyle.Value, out connRenderingStyle))
                                    {
                                        if (bpMetadata == null)
                                            bpMetadata = new BasePairMetadata(); 
                                        
                                        bpMetadata.ConnectorStyle = connRenderingStyle;
                                    }

                                    var strmdleconnectorproperties = strmdlpair.Element(RNAStructureModelXMLTags.StructureModelPairConnectorLabel);
                                    if (strmdleconnectorproperties != null &&
                                        !strmdleconnectorproperties.IsEmpty)
                                    {
                                        if (bpMetadata == null)
                                            bpMetadata = new BasePairMetadata();

                                        bpMetadata.ConnectorRendering = RNASecondaryStructureBasePairConnector.FromXML(strmdleconnectorproperties);
                                    }

                                    var strmdlpairntproperties = strmdlpair.Element(RNAStructureModelXMLTags.StructureModelPairNucleotideLabel);
                                    if (strmdlpairntproperties != null &&
                                        !strmdlpairntproperties.IsEmpty)
                                    {
                                        if (bpMetadata == null)
                                            bpMetadata = new BasePairMetadata();

                                        bpMetadata.NucleotideRenderingProperties = new NucleotideMetadata();

                                        NucleotideMetadata.RenderingMode localNtStyle;
                                        var ntpropblockstyle = strmdlpairntproperties.Element(RNAStructureModelXMLTags.StructureModelNucleotideRenderingStyleLabel);
                                        if (ntpropblockstyle != null &&
                                            !ntpropblockstyle.IsEmpty &&
                                            Enum.TryParse(ntpropblockstyle.Value, out localNtStyle))
                                            bpMetadata.NucleotideRenderingProperties.Style = localNtStyle;

                                        bool localNtVisibility;
                                        var ntpropblockvis = strmdlpairntproperties.Element(RNAStructureModelXMLTags.StructureModelNucleotideVisibilityLabel);
                                        if (ntpropblockvis != null &&
                                           !ntpropblockvis.IsEmpty &&
                                            bool.TryParse(ntpropblockvis.Value, out localNtVisibility))
                                            bpMetadata.NucleotideRenderingProperties.Visible = localNtVisibility;

                                        bool localNtIsSubstituted = false;
                                        char localNtSubstitution = ' ';
                                        var ntpropblocsubstitution = strmdlpairntproperties.Element(RNAStructureModelXMLTags.StructureModelNucleotideSubstitutionLabel);
                                        if (ntpropblocsubstitution != null &&
                                            !ntpropblocsubstitution.IsEmpty)
                                        {
                                            if (ntpropblocsubstitution.Value.Length >= 1)
                                            {
                                                localNtIsSubstituted = true;
                                                localNtSubstitution = ntpropblocsubstitution.Value[0];
                                            }
                                        }

                                        RNASecondaryStructureNucleotideTextShape ntTextRendering = RNASecondaryStructureNucleotideTextShape.FromXML(strmdlpairntproperties);
                                        RNASecondaryStructureNucleotideCircleShape ntCircleRendering = RNASecondaryStructureNucleotideCircleShape.FromXML(strmdlpairntproperties);

                                        bpMetadata.NucleotideRenderingProperties.IsSubstituted = localNtIsSubstituted;
                                        bpMetadata.NucleotideRenderingProperties.SubstitutionCharacter = localNtSubstitution;
                                        bpMetadata.NucleotideRenderingProperties.CircleRendering = ntCircleRendering;
                                        bpMetadata.NucleotideRenderingProperties.TextRendering = ntTextRendering;
                                    }

                                    if (bpMetadata != null)
                                        bpObj.SetExtendedProperty(BasePairMetadata.Key, bpMetadata);

                                    _basePairEntities.Add(bpObj);
                                }
                            }
                        }
                    }

                    //3. We look for a rendering, we will only set the positions initially and then calculate the vectors (to handle unspecified)
                    SequenceMetadata seqMetadata = new SequenceMetadata();
                    _sequence.SetExtendedProperty(SequenceMetadata.Key, seqMetadata);
                    var strmdlhasrenderingattr = strmdlxml.Root.Attribute(RNAStructureModelXMLTags.StructureModelHasRenderingAttribute);
                    bool bHasRendering = false;
                    if(strmdlhasrenderingattr!=null)
                        bool.TryParse(strmdlhasrenderingattr.Value, out bHasRendering);

                    if(bHasRendering)
                    {
                        var strmdlrendering = strmdlxml.Root.Element(RNAStructureModelXMLTags.StructureModelRenderingLabel);
                        if (strmdlrendering != null &&
                            !strmdlrendering.IsEmpty)
                        {
                            SequenceRendering rendering = LoadSequenceRendering(strmdlrendering.Descendants(RNAStructureModelXMLTags.StructureModelRenderingVectorLabel));
                            if (rendering != null && rendering.RenderingMap.Count == _sequence.SequenceData.Count)
                            {
                                seqMetadata.Data.Add(SequenceRendering.Key, rendering);
                            }
                        }
                    }

                    //4. We look to see if we have any global structure model properties
                    var strmdlmetadata = strmdlxml.Root.Element(RNAStructureModelXMLTags.StructureModelPropertiesLabel);
                    if (strmdlmetadata != null)
                    {
                        _globalstructureModelNucleotideTextRendering = RNASecondaryStructureNucleotideTextShape.FromXML(strmdlmetadata);
                        _globalstructureModelNucleotideCircleRendering = RNASecondaryStructureNucleotideCircleShape.FromXML(strmdlmetadata);
                        var strmdlmetadataglobalntrenderingmode = strmdlmetadata.Element(RNAStructureModelXMLTags.StructureModelNucleotideRenderingStyleLabel);
                        NucleotideMetadata.RenderingMode globalntrenderingmode;
                        if (strmdlmetadataglobalntrenderingmode != null && !strmdlmetadataglobalntrenderingmode.IsEmpty &&
                            Enum.TryParse<NucleotideMetadata.RenderingMode>(strmdlmetadataglobalntrenderingmode.Value, out globalntrenderingmode))
                            _globalstructureModelNucleotideRenderingMode = globalntrenderingmode;

                        _globalstructureModelBasePairConnectorRendering = RNASecondaryStructureBasePairConnector.FromXML(strmdlmetadata);
                        var strmdlmetadataglobalbpconnectorrenderingmode = strmdlmetadata.Element(RNAStructureModelXMLTags.StructureModelPairConnectorRenderingStyleLabel);
                        BasePairMetadata.ConnectorRenderingMode globalbpconnrenderingmode;
                        if (strmdlmetadataglobalbpconnectorrenderingmode != null && !strmdlmetadataglobalbpconnectorrenderingmode.IsEmpty &&
                            Enum.TryParse<BasePairMetadata.ConnectorRenderingMode>(strmdlmetadataglobalbpconnectorrenderingmode.Value, out globalbpconnrenderingmode))
                            _globalstructureModelBasePairConnectorRenderingMode = globalbpconnrenderingmode;

                        bool globalbpconnvisibility;
                        var strmdlmetadataglobalbpconnectorvisibility = strmdlmetadata.Element(RNAStructureModelXMLTags.StructureModelPairConnectorVisibleLabel);
                        if (strmdlmetadataglobalbpconnectorvisibility != null && !strmdlmetadataglobalbpconnectorvisibility.IsEmpty &&
                            bool.TryParse(strmdlmetadataglobalbpconnectorvisibility.Value, out globalbpconnvisibility))
                            _globalstructureModelBasePairConnectorIsVisible = globalbpconnvisibility;
                    }

                    //5. Now we look to see if we have any properties specified at a per nucleotide level
                    var strmdlntproperties = strmdlxml.Root.Element(RNAStructureModelXMLTags.StructureModelNucleotidePropertiesLabel);
                    if (strmdlntproperties != null &&
                        !strmdlntproperties.IsEmpty)
                    {
                        foreach (var ntpropblock in strmdlntproperties.Descendants(RNAStructureModelXMLTags.StructureModelNucleotideLabel))
                        {
                            int ntIdx = -1;
                            var ntpropblockidx = ntpropblock.Element(RNAStructureModelXMLTags.StructureModelNucleotideIndexLabel);
                            if (ntpropblockidx != null &&
                                !ntpropblockidx.IsEmpty &&
                                Int32.TryParse(ntpropblockidx.Value, out ntIdx) &&
                                ntIdx > 0 && ntIdx <= _sequence.SequenceData.Count)
                            {
                                ntIdx = ntIdx - 1; //Correct for the 1s based format indexing
                                NucleotideMetadata metadata = new NucleotideMetadata();
                                
                                NucleotideMetadata.RenderingMode localNtStyle;
                                var ntpropblockstyle = ntpropblock.Element(RNAStructureModelXMLTags.StructureModelNucleotideRenderingStyleLabel);
                                if (ntpropblockstyle != null &&
                                    !ntpropblockstyle.IsEmpty &&
                                    Enum.TryParse(ntpropblockstyle.Value, out localNtStyle))
                                    metadata.Style = localNtStyle;

                                bool localNtVisibility;
                                var ntpropblockvis = ntpropblock.Element(RNAStructureModelXMLTags.StructureModelNucleotideVisibilityLabel);
                                if (ntpropblockvis != null &&
                                   !ntpropblockvis.IsEmpty &&
                                    bool.TryParse(ntpropblockvis.Value, out localNtVisibility))
                                    metadata.Visible = localNtVisibility;

                                bool localNtIsSubstituted = false;
                                char localNtSubstitution = ' ';
                                var ntpropblocsubstitution = ntpropblock.Element(RNAStructureModelXMLTags.StructureModelNucleotideSubstitutionLabel);
                                if (ntpropblocsubstitution != null &&
                                    !ntpropblocsubstitution.IsEmpty)
                                {
                                    if (ntpropblocsubstitution.Value.Length >= 1)
                                    {
                                        localNtIsSubstituted = true;
                                        localNtSubstitution = ntpropblocsubstitution.Value[0];
                                    }
                                }

                                RNASecondaryStructureNucleotideTextShape ntTextRendering = RNASecondaryStructureNucleotideTextShape.FromXML(ntpropblock);
                                RNASecondaryStructureNucleotideCircleShape ntCircleRendering = RNASecondaryStructureNucleotideCircleShape.FromXML(ntpropblock);

                                metadata.IsSubstituted = localNtIsSubstituted;
                                metadata.SubstitutionCharacter = localNtSubstitution;
                                metadata.CircleRendering = ntCircleRendering;
                                metadata.TextRendering = ntTextRendering;

                                IExtensibleProperties ntprops = _sequence.SequenceData[ntIdx] as IExtensibleProperties;
                                if (ntprops != null)
                                {
                                    ntprops.SetExtendedProperty(NucleotideMetadata.Key, metadata);
                                }
                            }
                        }
                    }

                    return true;
                }
            }
            return false;
        }

        protected override void OnLoad()
        {
            try
            {
                RNAParser labelRNAParser = new RNAParser(_basePairs, _sequence.SequenceData.Count);
                XElement extentsXML = labelRNAParser.ParseToExtents();
                var extents = from extent in extentsXML.Descendants("Extent")
                              select new RNASecondaryStructureExtent(_sequence,
                                                                   Int32.Parse(extent.Element("ExtentID").Value),
                                                                   Int32.Parse(extent.Element("ExtentOrdinal").Value),
                                                                   (StructureExtentType)Enum.Parse(typeof(StructureExtentType), extent.Element("ExtentTypeID").Value, true),
                                                                   Int32.Parse(extent.Element("ExtentStartIndex").Value),
                                                                   Int32.Parse(extent.Element("ExtentEndIndex").Value));
                
                RNASecondaryStructureModel model = new RNASecondaryStructureModel(InitializationData, _sequence)
                {
                    BasePairs = _basePairEntities.Cast<IBasePairBioEntity>().ToList(),
                    Extents = extents.Cast<IStructureExtentBioEntity>().ToList()
                };
                model.SetExtendedProperty(StructureModelMetadata.Key, new StructureModelMetadata());

                if (_globalstructureModelNucleotideRenderingMode != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).NucleotideRenderingMode = _globalstructureModelNucleotideRenderingMode;

                if (_globalstructureModelNucleotideCircleRendering != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).NucleotideCircleRendering = _globalstructureModelNucleotideCircleRendering;

                if (_globalstructureModelNucleotideTextRendering != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).NucleotideTextRendering = _globalstructureModelNucleotideTextRendering;

                if (_globalstructureModelBasePairConnectorIsVisible != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).ConnectorVisible = _globalstructureModelBasePairConnectorIsVisible;

                if (_globalstructureModelBasePairConnectorRenderingMode != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).ConnectorStyle = _globalstructureModelBasePairConnectorRenderingMode;

                if (_globalstructureModelBasePairConnectorRendering != null)
                    model.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key).ConnectorRendering = _globalstructureModelBasePairConnectorRendering;

                Entities.Add(model);
            }
            catch (Exception) //We ran into some error parsing the extents.
            {
                Entities.Add(new RNASecondaryStructureModel(InitializationData, _sequence)
                {
                    BasePairs = _basePairEntities.Cast<IBasePairBioEntity>().ToList()
                });
            }
        }

        private SequenceRendering LoadSequenceRendering(IEnumerable<XElement> vectors)
        {
            Debug.Assert(vectors != null);
            SequenceRendering retValue = new SequenceRendering();

            //1. Initialize the empty rendering map.
            for (int i = 0; i < _sequence.SequenceData.Count; i++)
                retValue.RenderingMap.Add(i, null);

            //2. Load the renderings we have.
            foreach (var rendervectxml in vectors)
            {
                RNASecondaryStructureNucleotideVector rnaNTVector = RNASecondaryStructureNucleotideVector.FromXML(rendervectxml);
                if (rnaNTVector != null &&
                    rnaNTVector.NtIndex >=0 &&
                    rnaNTVector.NtIndex < _sequence.SequenceData.Count)
                {
                    retValue.RenderingMap[rnaNTVector.NtIndex] = rnaNTVector;
                }
            }

            //3. Fill in any "holes"
            List<int> unmappedNtIdx = new List<int>();
            foreach (var key in retValue.RenderingMap.Keys.OrderBy(k => k))
            {
                if (retValue.RenderingMap[key] == null)
                    unmappedNtIdx.Add(key);
                else
                {
                    //We hit a group.
                    PositionUnmappedNucleotides(key, unmappedNtIdx, retValue);
                    unmappedNtIdx.Clear();
                }
            }

            if (unmappedNtIdx.Count > 0)
            {
                int anchorPos = retValue.RenderingMap.Last(kvp => kvp.Value != null).Key;
                PositionUnmappedNucleotides(anchorPos, unmappedNtIdx, retValue);
                unmappedNtIdx.Clear();
            }

            //4. Recompute all the rendering vectors
            var allNt = retValue.RenderingMap.Keys.OrderBy(k => k);
            foreach (var nt in allNt)
            {
                int nextNt = (nt + 1);
                if (nextNt < retValue.RenderingMap.Keys.Count)
                {
                    IVector ntVector = new RNASecondaryStructureNucleotideVector(retValue.RenderingMap[nt].StartX, retValue.RenderingMap[nt].StartY,
                        retValue.RenderingMap[nextNt].StartX, retValue.RenderingMap[nextNt].StartY);
                    retValue.RenderingMap[nt] = ntVector;
                }
            }

            return retValue;
        }

        private void PositionUnmappedNucleotides(int firstNtInGroupIdx, List<int> unmappedNtIdx, SequenceRendering sequenceRendering)
        {
            if (unmappedNtIdx != null && unmappedNtIdx.Count > 0)
            {
                //We will arrange the nt next to the first group member in a grid.
                if (firstNtInGroupIdx != -1)
                    sequenceRendering.RenderingMap[unmappedNtIdx.First()] = new RNASecondaryStructureNucleotideVector(sequenceRendering.RenderingMap[firstNtInGroupIdx].StartX + DEFAULT_NT_SPACING, sequenceRendering.RenderingMap[firstNtInGroupIdx].StartY);

                if (unmappedNtIdx.Count > 1)
                {
                    //Intermediate points.
                    int rowCount = 1;
                    double anchorXPos = sequenceRendering.RenderingMap[unmappedNtIdx.First()].StartX;
                    for (int i = 1; i < (unmappedNtIdx.Count - 1); i++)
                    {
                        if (rowCount % 5 == 0)
                        {
                            sequenceRendering.RenderingMap[unmappedNtIdx[i]] = new RNASecondaryStructureNucleotideVector(anchorXPos, sequenceRendering.RenderingMap[unmappedNtIdx[i] - 1].StartY + DEFAULT_NT_SPACING);
                        }
                        else
                        {
                            sequenceRendering.RenderingMap[unmappedNtIdx[i]] = new RNASecondaryStructureNucleotideVector(sequenceRendering.RenderingMap[unmappedNtIdx[i - 1]].StartX + DEFAULT_NT_SPACING, sequenceRendering.RenderingMap[unmappedNtIdx[i - 1]].StartY);
                        }

                        rowCount++;
                    }

                    //Now we will handle the last point, we'll put it to the right of the previous one
                    sequenceRendering.RenderingMap[unmappedNtIdx.Last()] = new RNASecondaryStructureNucleotideVector(sequenceRendering.RenderingMap[unmappedNtIdx.Last() - 1].StartX + DEFAULT_NT_SPACING, sequenceRendering.RenderingMap[unmappedNtIdx.Last() - 1].StartY);
                }
            }
            unmappedNtIdx.Clear();
        }

        private static double DEFAULT_NT_SPACING = 8.0;
    }
}
